package io.intodream.util.utils.util;

import io.intodream.util.utils.entity.Person;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.validation.constraints.NotNull;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;

/**
 * {描述}
 *
 * @author yangxianxi@gogpay.cn
 * @date 2018/7/6 14:15
 */
public class Convert {
    private static final Logger logger = LoggerFactory.getLogger(Convert.class);

    /**
     * 配置文件流
     */
    private  InputStream inputStream;

    /**
     * 配置文件键值对
     */
    private Map<String, String> paramsMap;

    private Map<String, String> keysMap;


    /**
     * 如果需要使用指定的配置文件，则需要传入配置文件流，且文件不能为空
     * @param inputStream 配置文件流
     * @return
     */
    public static Convert of(@NotNull(message = "配置文件不能为空") InputStream inputStream) {
        Convert convert = new Convert(inputStream);
        loadConfig(convert);
        return convert;
    }

    /**
     * 通过静态方法创建一个对象
     * @return
     */
    public static Convert now() {
        return new Convert();
    }

    /**
     * 实现将Map对象转换为JavaBean对象，目的是为了解决属性名不一样的对象值转换
     * @param json
     * @param clazz
     * @param <T>
     * @return
     */
    public <T> T mapConvert2Obj(@NotNull Map json, @NotNull Class<T> clazz){
        Object object = null;
        try {
            object = clazz.newInstance();
        } catch (InstantiationException e) {
            logger.error("实例化对象异常", e);
        } catch (IllegalAccessException e) {
            logger.error("方法访问权限异常", e);
        }
        // 遍历Map里面的key和value
        Object finalObject = object;
        // 如果不是使用指定的配置文件来转换的情况下，传入的map KEY就是JavaBean的属性名
        json.forEach((k, v)-> setValue(paramsMap == null ? k.toString() : paramsMap.get(k), v, finalObject));
        return (T)finalObject;
    }

    /**
     * 对象转换为Map
     * @param object
     * @return
     */
    public Map objectConvert2Map(@NotNull Object object) {
        return getObjectValues(object);
    }

    /**
     * 获取JavaBean里面的属性名及属性值，封装成Map后返回
     * @param object
     * @return
     */
    private Map<String, Object> getObjectValues(@NotNull Object object) {

        Field[] fields = object.getClass().getDeclaredFields();
        Map<String,Object> valuesMap = new HashMap<>(fields.length);
        for (Field f : fields) {
            String filedName = f.getName();
            //获取首字母并大写
            String firstGetter = filedName.substring(0, 1).toUpperCase();
            //执行反射获取属性值
            String methodName = "get" + firstGetter + filedName.substring(1);
            try {
                Method method = object.getClass().getMethod(methodName,new Class[] {});
                Object value = method.invoke(object, new Object[] {});
                // 获取key,考虑到兼容性，这里有两种情况，如果使用场景是基于配置文件的情况下，key值配置文件中的，如果不是则使用的是属性名称
                String key = (keysMap == null || keysMap.isEmpty()) ? filedName : keysMap.get(filedName);
                valuesMap.put(key, value);
            } catch (NoSuchMethodException e) {
                logger.error("没有找到对应的方法", e);
            } catch (IllegalAccessException e) {
                logger.error("没有构造方法或没有权限访问构造方法", e);
            } catch (InvocationTargetException e) {
                logger.error("反射异常，来之反射对象内部", e);
            }
        }
        return valuesMap;
    }

    /**
     * 利用反射技术进行赋值
     * @param fieldName
     * @param value
     * @param obj
     */
    private void setValue(@NotNull String fieldName, @NotNull Object value, @NotNull Object obj){
        // 利用反射技术动态赋值
        // 构造一个set方法
        String setting = "set";
        // 获取首字符大写
        String firstName = fieldName.substring(0,1).toUpperCase();
        setting = setting + firstName + fieldName.substring(1);
        try {
            Method method = obj.getClass().getMethod(setting,value.getClass());
            method.invoke(obj,value);
        } catch (NoSuchMethodException e) {
            logger.error("没有找到对应的方法", e);
        } catch (IllegalAccessException e) {
            logger.error("方法访问权限异常", e);
        } catch (InvocationTargetException e) {
            logger.error("未知异常", e);
        }
    }

    /**
     * 私有化构造方法
     * @param inputStream
     */
    private Convert(InputStream inputStream) {
        this.inputStream  = inputStream;
    }

    /**
     * 私有化构造方法
     */
    private Convert(){}

    /**
     * 加载配置文件
     * @param convert
     * @return
     */
    private static Map<String, String> loadConfig(@NotNull Convert convert) {
        Properties prop = new Properties();
        try{
            prop.load(convert.inputStream);
            Map<String, String> params = new HashMap<>(prop.size());
            Map<String, String> keysMap = new HashMap<>(prop.size());
            initMap(prop, params, keysMap);
            convert.setKeysMap(keysMap);
            convert.setParamsMap(params);
            return params;
        }catch (IOException e){
            logger.error("读取配置文件出错", e);
        } finally {
            if (Objects.nonNull(convert.inputStream)) {
                try {
                    convert.inputStream.close();
                } catch (IOException e) {
                    logger.info("关闭inputStream发生错误", e);
                }
            }
        }
        return null;
    }

    /**
     * 初始化Map
     * @param prop
     * @param params
     */
    private static void initMap(@NotNull Properties prop, Map<String, String> params, Map<String, String> keysMap){
        Enumeration en = prop.propertyNames();
        while (en.hasMoreElements()) {
            String key = (String) en.nextElement();
            String value = prop.getProperty(key);
            params.put(key, value);
            keysMap.put(value, key);
        }
    }

    private void setParamsMap(Map<String, String> paramsMap) {
        this.paramsMap = paramsMap;
    }

    private void setKeysMap(Map<String, String> keysMap) {
        this.keysMap = keysMap;
    }

    public static void main(String[] args) {
        InputStream inputStream = Convert.class.getClassLoader().getResourceAsStream("api-param.properties");
//        String configName =  "api-param.properties";
        Convert convertUtils2 = Convert.of(inputStream);
        logger.info("转换工具:{}", convertUtils2);
        Person person = new Person("张三", "18525589639", 50);
        Map map = Convert.now().objectConvert2Map(person);
        logger.info("Object2Map:{}", map);
    }
}
